var io = require('socket.io-client');

var ace = require('brace');
require('brace/mode/javascript');

var editor = ace.edit('code');
editor.getSession().setMode('ace/mode/javascript');
editor.setOptions({
    fontFamily: "monospace",
    fontSize: "12px",
    fixedWidthGutter: true
});
editor.renderer.setShowPrintMargin(false);
editor.renderer.setShowGutter(false);

var localStorage = window.localStorage ? window.localStorage : {
    getItem: function () {},
    setItem: function () {}
}; // mock

var playerName;
var playerType;
var playerNameInput = document.getElementById('playerNameInput');
var socket;
var reason;
var KEY_ESC = 27;
var KEY_ENTER = 13;
var KEY_CHAT = 13;
var borderDraw = false;
var animLoopHandle;
var spin = -Math.PI;
var enemySpin = -Math.PI;
var foodSides = 10;
var virusSides = 20;

var userCode = "";

var debug = function(args) {
    if (console && console.log) {
        console.log(args);
    }
};

function startGame(type) {
    playerName = playerNameInput.value.replace(/(<([^>]+)>)/ig, '').substring(0,25);
    playerType = type;

    screenWidth = window.innerWidth;
    screenHeight = window.innerHeight;

    document.getElementById('startMenuWrapper').style.maxHeight = '0px';
    document.getElementById('gameAreaWrapper').style.opacity = 1;
    document.getElementById('gameAreaWrapper').style.visibility = "visible";

    document.getElementById('runCode').addEventListener('click', function () {
        var code = document.getElementById('codeInput').value;

        c.focus();

        if (typeof(code) == "undefined" || code.trim() === "")
        {
            return;
        }

        setTimeout(code, 0);
    });

    if (!socket) {
        socket = io({query:"type=" + type});
        setupSocket(socket);
    }
    if (!animLoopHandle)
        animloop();
    socket.emit('respawn');
}

// Checks if the nick chosen contains valid alphanumeric characters (and underscores).
function validNick() {
    var regex = /^\w*$/;
    debug('Regex Test', regex.exec(playerNameInput.value));
    return regex.exec(playerNameInput.value) !== null;
}

window.onload = function() {

    var btn = document.getElementById('startButton'),
        btnS = document.getElementById('spectateButton'),
        nickErrorText = document.querySelector('#startMenu .input-error'),
        codeErrorText = document.querySelector('#startMenu .code-error');

    btnS.onclick = function () {
        startGame('spectate');
    };

    btn.onclick = function () {
        var code = editor.getValue();

        if (typeof(code) == "undefined" || code.trim() === "")
        {
            codeErrorText.style.opacity = 1;
            return;
        }

        // Checks if the nick is valid.
        if (validNick()) {
            nickErrorText.style.opacity = 0;
            localStorage.setItem('lastPlayedCode', code);
            setTimeout(code, 0);
            startGame('player');
        } else {
            nickErrorText.style.opacity = 1;
        }
    };

    var settingsMenu = document.getElementById('settingsButton');
    var settings = document.getElementById('settings');
    var instructions = document.getElementById('instructions');

    settingsMenu.onclick = function () {
        if (settings.style.maxHeight == '300px') {
            settings.style.maxHeight = '0px';
        } else {
            settings.style.maxHeight = '300px';
        }
    };

    playerNameInput.addEventListener('keypress', function (e) {
        var key = e.which || e.keyCode;

        if (key === KEY_ENTER) {
            if (validNick()) {
                nickErrorText.style.opacity = 0;
                startGame('player');
            } else {
                nickErrorText.style.opacity = 1;
            }
        }
    });

    var lastCode = localStorage.getItem('lastPlayedCode');
    if (typeof(lastCode) != "undefined" && lastCode !== null)
    {
        editor.setValue(lastCode);
    }
};

// Canvas.
var screenWidth = window.innerWidth;
var screenHeight = window.innerHeight;
var gameWidth = 0;
var gameHeight = 0;
var xoffset = -gameWidth;
var yoffset = -gameHeight;

var gameStart = false;
var disconnected = false;
var died = false;
var kicked = false;
var located = false;

var startPingTime = 0;
var toggleMassState = 0;
var backgroundColor = '#f2fbff';
var lineColor = '#000000';

var foodConfig = {
    border: 0,
};

var playerConfig = {
    border: 6,
    textColor: '#FFFFFF',
    textBorder: '#000000',
    textBorderSize: 3,
    defaultSize: 30
};

var player = {
    id: -1,
    x: screenWidth / 2,
    y: screenHeight / 2,
    screenWidth: screenWidth,
    screenHeight: screenHeight,
    target: {x: screenWidth / 2, y: screenHeight / 2}
};

var foods = [];
var viruses = [];
var fireFood = [];
var users = [];
var leaderboard = [];
var target = {x: 0, y: 0};
var directionLock = false;
var directions = [];

var realTarget = {x: 0, y: 0};

var c = document.getElementById('cvs');
c.width = screenWidth; c.height = screenHeight;

var visibleBorderSetting = document.getElementById('visBord');
visibleBorderSetting.onchange = toggleBorder;

var showMassSetting = document.getElementById('showMass');
showMassSetting.onchange = toggleMass;

var roundFoodSetting = document.getElementById('roundFood');
roundFoodSetting.onchange = toggleRoundFood;

var graph = c.getContext('2d');

function ChatClient(config) {
    this.commands = {};
    var input = document.getElementById('chatInput');
    input.addEventListener('keypress', this.sendChat.bind(this));
    input.addEventListener('keyup', function(key) {
        input = document.getElementById('chatInput');

        key = key.which || key.keyCode;
        if (key === KEY_ESC) {
            input.value = '';
            c.focus();
        }
    });
}

// Chat box implementation for the users.
ChatClient.prototype.addChatLine = function (name, message, me) {
    var newline = document.createElement('li');

    // Colours the chat input correctly.
    newline.className = (me) ? 'me' : 'friend';
    newline.innerHTML = '<b>' + ((name.length < 1) ? 'An unnamed cell' : name) + '</b>: ' + message;

    this.appendMessage(newline);
};


// Chat box implementation for the system.
ChatClient.prototype.addSystemLine = function (message) {
    var newline = document.createElement('li');

    // Colours the chat input correctly.
    newline.className = 'system';
    newline.innerHTML = message;

    // Append messages to the logs.
    this.appendMessage(newline);
};

ChatClient.prototype.addDebugLine = function (message) {
    var newline = document.createElement('li');

    // Colours the chat input correctly.
    newline.className = 'output';
    newline.innerHTML = message;

    // Append messages to the logs.
    this.appendMessage(newline);
};

// Places the message DOM node into the chat box.
ChatClient.prototype.appendMessage = function (node) {
    var chatList = document.getElementById('chatList');
    if (chatList.childNodes.length > 10) {
        chatList.removeChild(chatList.childNodes[0]);
    }
    chatList.appendChild(node);
};

// Sends a message or executes a command on the click of enter.
ChatClient.prototype.sendChat = function (key) {
    var commands = this.commands,
        input = document.getElementById('chatInput');

    key = key.which || key.keyCode;

    if (key === KEY_ENTER) {
        var text = input.value.replace(/(<([^>]+)>)/ig,'');
        if (text !== '') {

            // Chat command.
            if (text.indexOf('-') === 0) {
                var args = text.substring(1).split(' ');
                if (commands[args[0]]) {
                    commands[args[0]].callback(args.slice(1));
                } else {
                    this.addSystemLine('Unrecognized Command: ' + text + ', type -help for more info.');
                }

            // Allows for regular messages to be sent to the server.
            } else {
                socket.emit('playerChat', { sender: player.name, message: text });
                this.addChatLine(player.name, text, true);
            }

            // Resets input.
            input.value = '';
            c.focus();
        }
    }
};

// Allows for addition of commands.
ChatClient.prototype.registerCommand = function (name, description, callback) {
    this.commands[name] = {
        description: description,
        callback: callback
    };
};

// Allows help to print the list of all the commands and their descriptions.
ChatClient.prototype.printHelp = function () {
    var commands = this.commands;
    for (var cmd in commands) {
        if (commands.hasOwnProperty(cmd)) {
            this.addSystemLine('-' + cmd + ': ' + commands[cmd].description);
        }
    }
};

var chat = new ChatClient();

function checkLatency() {
    // Ping.
    startPingTime = Date.now();
    socket.emit('ping');
}

function toggleDarkMode() {
    var LIGHT = '#f2fbff',
        DARK = '#181818';
    var LINELIGHT = '#000000',
        LINEDARK = '#ffffff';

    if (backgroundColor === LIGHT) {
        backgroundColor = DARK;
        lineColor = LINEDARK;
        chat.addSystemLine('Dark mode enabled.');
    } else {
        backgroundColor = LIGHT;
        lineColor = LINELIGHT;
        chat.addSystemLine('Dark mode disabled.');
    }
}

function toggleBorder() {
    if (!borderDraw) {
        borderDraw = true;
        chat.addSystemLine('Showing border.');
    } else {
        borderDraw = false;
        chat.addSystemLine('Hiding border.');
    }
}

function toggleMass() {
    if (toggleMassState === 0) {
        toggleMassState = 1;
        chat.addSystemLine('Viewing mass enabled.');
    } else {
        toggleMassState = 0;
        chat.addSystemLine('Viewing mass disabled.');
    }
}

function toggleRoundFood(args) {
    if (foodSides < 10) {
        foodSides = (args && !isNaN(args[0]) && +args[0] >= 3) ? +args[0] : 10;
        chat.addSystemLine('Food is now rounded!');
    } else {
        foodSides = 5;
        chat.addSystemLine('Food is no longer rounded!');
    }
}

// TODO: Break out many of these GameControls into separate classes.

chat.registerCommand('ping', 'Check your latency.', function () {
    checkLatency();
});

chat.registerCommand('dark', 'Toggle dark mode.', function () {
    toggleDarkMode();
});

chat.registerCommand('border', 'Toggle visibility of border.', function () {
    toggleBorder();
});

chat.registerCommand('mass', 'Toggle visibility of mass.', function () {
    toggleMass();
});

chat.registerCommand('roundfood', 'Toggle food drawing.', function (args) {
    toggleRoundFood(args);
});

chat.registerCommand('help', 'Information about the chat commands.', function () {
    chat.printHelp();
});

chat.registerCommand('login', 'Login as an admin.', function (args) {
    socket.emit('pass', args);
});

chat.registerCommand('kick', 'Kick a player, for admins only.', function (args) {
    socket.emit('kick', args);
});

// socket stuff.
function setupSocket(socket) {
    // Handle ping.
    socket.on('pong', function (args) {
        var latency = Date.now() - startPingTime;
        if (typeof(args) === "number")
        {
            latency = args;
        }
        debug('Latency: ' + latency + 'ms');
        chat.addSystemLine('Ping: ' + latency + 'ms');
    });

    // Handle error.
    socket.on('connect_failed', function () {
        socket.close();
        disconnected = true;
    });

    socket.on('disconnect', function () {
        socket.close();
        disconnected = true;
    });

    // Handle connection.
    socket.on('welcome', function (playerSettings) {
        player = playerSettings;
        player.name = playerName;
        player.screenWidth = screenWidth;
        player.screenHeight = screenHeight;
        player.target = target;
        socket.emit('gotit', player);
        gameStart = true;
        debug('Game started at: ' + gameStart);
        chat.addSystemLine('Connected to the game!');
        chat.addSystemLine('Type <b>-help</b> for a list of commands.');
        c.focus();
    });

    socket.on('gameSetup', function(data) {
        gameWidth = data.gameWidth;
        gameHeight = data.gameHeight;
        resizeGameArea();
        ready();
    });

    socket.on('playerDied', function (data) {
        chat.addSystemLine('{GAME} - <b>' + (data.name.length < 1 ? 'An unnamed cell' : data.name) + '</b> was eaten.');
    });

    socket.on('playerDisconnect', function (data) {
        chat.addSystemLine('{GAME} - <b>' + (data.name.length < 1 ? 'An unnamed cell' : data.name) + '</b> disconnected.');
    });

    socket.on('playerJoin', function (data) {
        chat.addSystemLine('{GAME} - <b>' + (data.name.length < 1 ? 'An unnamed cell' : data.name) + '</b> joined.');
    });

    socket.on('leaderboard', function (data) {
        leaderboard = data.leaderboard;
        var status = '<span class="title">Leaderboard</span>';
        for (var i = 0; i < leaderboard.length; i++) {
            status += '<br />';
            if (leaderboard[i].id == player.id){
                if(leaderboard[i].name.length !== 0)
                    status += '<span class="me">' + (i + 1) + '. ' + leaderboard[i].name + "</span>";
                else
                    status += '<span class="me">' + (i + 1) + ". An unnamed cell</span>";
            } else {
                if(leaderboard[i].name.length !== 0)
                    status += (i + 1) + '. ' + leaderboard[i].name;
                else
                    status += (i + 1) + '. An unnamed cell';
            }
        }
        //status += '<br />Players: ' + data.players;
        document.getElementById('status').innerHTML = status;
    });

    socket.on('serverMSG', function (data) {
        chat.addSystemLine(data);
    });

    // Chat.
    socket.on('serverSendPlayerChat', function (data) {
        chat.addChatLine(data.sender, data.message, false);
    });

    // Handle movement.
    socket.on('serverTellPlayerMove', function (userData, foodsList, massList, virusList) {
        var playerData;
        for(var i =0; i< userData.length; i++) {
            if(typeof(userData[i].id) == "undefined") {
                playerData = userData[i];
                i = userData.length;
            }
        }
        if(playerType == 'player') {
            var xoffset = player.x - playerData.x;
            var yoffset = player.y - playerData.y;

            player.x = playerData.x;
            player.y = playerData.y;
            player.hue = playerData.hue;
            player.massTotal = playerData.massTotal;
            player.cells = playerData.cells;
            player.xoffset = isNaN(xoffset) ? 0 : xoffset;
            player.yoffset = isNaN(yoffset) ? 0 : yoffset;
        }
        users = userData;
        foods = foodsList;
        viruses = virusList;
        fireFood = massList;

        if (located === false)
        {
            located = true;
            realTarget = {
                x: player.x,
                y: player.y
            };
        }

        setTimeout(userCode, 0);
    });

    // Death.
    socket.on('RIP', function () {
        gameStart = false;
        died = true;
        window.setTimeout(function() {
            document.getElementById('gameAreaWrapper').style.opacity = 0;
            document.getElementById('gameAreaWrapper').style.visibility = "hidden";
            document.getElementById('startMenuWrapper').style.maxHeight = '1000px';
            died = false;
            if (animLoopHandle) {
                window.cancelAnimationFrame(animLoopHandle);
                animLoopHandle = undefined;
            }
        }, 2500);
    });

    socket.on('kick', function (data) {
        gameStart = false;
        reason = data;
        kicked = true;
        socket.close();
    });
}

function drawCircle(centerX, centerY, radius, sides) {
    var theta = 0;
    var x = 0;
    var y = 0;

    graph.beginPath();

    for (var i = 0; i < sides; i++) {
        theta = (i / sides) * 2 * Math.PI;
        x = centerX + radius * Math.sin(theta);
        y = centerY + radius * Math.cos(theta);
        graph.lineTo(x, y);
    }

    graph.closePath();
    graph.stroke();
    graph.fill();
}

function drawFood(food) {
    graph.strokeStyle = 'hsl(' + food.hue + ', 100%, 45%)';
    graph.fillStyle = 'hsl(' + food.hue + ', 100%, 50%)';
    graph.lineWidth = foodConfig.border;
    drawCircle(food.x - player.x + screenWidth / 2, food.y - player.y + screenHeight / 2, food.radius, foodSides);
}

function drawVirus(virus) {
    graph.strokeStyle = virus.stroke;
    graph.fillStyle = virus.fill;
    graph.lineWidth = virus.strokeWidth;
    drawCircle(virus.x - player.x + screenWidth / 2, virus.y - player.y + screenHeight / 2, virus.radius, virusSides);
}

function drawFireFood(mass) {
    graph.strokeStyle = 'hsl(' + mass.hue + ', 100%, 45%)';
    graph.fillStyle = 'hsl(' + mass.hue + ', 100%, 50%)';
    graph.lineWidth = playerConfig.border+10;
    drawCircle(mass.x - player.x + screenWidth / 2, mass.y - player.y + screenHeight / 2, mass.radius-5, 18 + (~~(mass.masa/5)));
}

function drawPlayers(order) {
    var start = {
        x: player.x - (screenWidth / 2),
        y: player.y - (screenHeight / 2)
    };

    for(var z=0; z<order.length; z++)
    {
        var userCurrent = users[order[z].nCell];
        var cellCurrent = users[order[z].nCell].cells[order[z].nDiv];

        var x=0;
        var y=0;

        var points = 30 + ~~(cellCurrent.mass/5);
        var increase = Math.PI * 2 / points;

        graph.strokeStyle = 'hsl(' + userCurrent.hue + ', 100%, 45%)';
        graph.fillStyle = 'hsl(' + userCurrent.hue + ', 100%, 50%)';
        graph.lineWidth = playerConfig.border;

        var xstore = [];
        var ystore = [];

        spin += 0.0;

        var circle = {
            x: cellCurrent.x - start.x,
            y: cellCurrent.y - start.y
        };

        for (var i = 0; i < points; i++) {

            x = cellCurrent.radius * Math.cos(spin) + circle.x;
            y = cellCurrent.radius * Math.sin(spin) + circle.y;
            if(typeof(userCurrent.id) == "undefined") {
                x = valueInRange(-userCurrent.x + screenWidth / 2, gameWidth - userCurrent.x + screenWidth / 2, x);
                y = valueInRange(-userCurrent.y + screenHeight / 2, gameHeight - userCurrent.y + screenHeight / 2, y);
            } else {
                x = valueInRange(-cellCurrent.x - player.x + screenWidth/2 + (cellCurrent.radius/3), gameWidth - cellCurrent.x + gameWidth - player.x + screenWidth/2 - (cellCurrent.radius/3), x);
                y = valueInRange(-cellCurrent.y - player.y + screenHeight/2 + (cellCurrent.radius/3), gameHeight - cellCurrent.y + gameHeight - player.y + screenHeight/2 - (cellCurrent.radius/3) , y);
            }
            spin += increase;
            xstore[i] = x;
            ystore[i] = y;
        }
        /*if (wiggle >= player.radius/ 3) inc = -1;
        *if (wiggle <= player.radius / -3) inc = +1;
        *wiggle += inc;
        */
        for (i = 0; i < points; ++i) {
            if (i === 0) {
                graph.beginPath();
                graph.moveTo(xstore[i], ystore[i]);
            } else if (i > 0 && i < points - 1) {
                graph.lineTo(xstore[i], ystore[i]);
            } else {
                graph.lineTo(xstore[i], ystore[i]);
                graph.lineTo(xstore[0], ystore[0]);
            }

        }
        graph.lineJoin = 'round';
        graph.lineCap = 'round';
        graph.fill();
        graph.stroke();
        var nameCell = "";
        if(typeof(userCurrent.id) == "undefined")
            nameCell = player.name;
        else
            nameCell = userCurrent.name;

        var fontSize = Math.max(cellCurrent.radius / 3, 12);
        graph.lineWidth = playerConfig.textBorderSize;
        graph.fillStyle = playerConfig.textColor;
        graph.strokeStyle = playerConfig.textBorder;
        graph.miterLimit = 1;
        graph.lineJoin = 'round';
        graph.textAlign = 'center';
        graph.textBaseline = 'middle';
        graph.font = 'bold ' + fontSize + 'px sans-serif';

        if (toggleMassState === 0) {
            graph.strokeText(nameCell, circle.x, circle.y);
            graph.fillText(nameCell, circle.x, circle.y);
        } else {
            graph.strokeText(nameCell, circle.x, circle.y);
            graph.fillText(nameCell, circle.x, circle.y);
            graph.font = 'bold ' + Math.max(fontSize / 3 * 2, 10) + 'px sans-serif';
            if(nameCell.length === 0) fontSize = 0;
            graph.strokeText(Math.round(cellCurrent.mass), circle.x, circle.y+fontSize);
            graph.fillText(Math.round(cellCurrent.mass), circle.x, circle.y+fontSize);
        }
    }
}

function valueInRange(min, max, value) {
    return Math.min(max, Math.max(min, value));
}

function drawgrid() {
     graph.lineWidth = 1;
     graph.strokeStyle = lineColor;
     graph.globalAlpha = 0.15;
     graph.beginPath();

    for (var x = xoffset - player.x; x < screenWidth; x += screenHeight / 18) {
        graph.moveTo(x, 0);
        graph.lineTo(x, screenHeight);
    }

    for (var y = yoffset - player.y ; y < screenHeight; y += screenHeight / 18) {
        graph.moveTo(0, y);
        graph.lineTo(screenWidth, y);
    }

    graph.stroke();
    graph.globalAlpha = 1;
}

function drawborder() {
    graph.lineWidth = 1;
    graph.strokeStyle = playerConfig.borderColor;

    // Left-vertical.
    if (player.x <= screenWidth/2) {
        graph.beginPath();
        graph.moveTo(screenWidth/2 - player.x, 0 ? player.y > screenHeight/2 : screenHeight/2 - player.y);
        graph.lineTo(screenWidth/2 - player.x, gameHeight + screenHeight/2 - player.y);
        graph.strokeStyle = lineColor;
        graph.stroke();
    }

    // Top-horizontal.
    if (player.y <= screenHeight/2) {
        graph.beginPath();
        graph.moveTo(0 ? player.x > screenWidth/2 : screenWidth/2 - player.x, screenHeight/2 - player.y);
        graph.lineTo(gameWidth + screenWidth/2 - player.x, screenHeight/2 - player.y);
        graph.strokeStyle = lineColor;
        graph.stroke();
    }

    // Right-vertical.
    if (gameWidth - player.x <= screenWidth/2) {
        graph.beginPath();
        graph.moveTo(gameWidth + screenWidth/2 - player.x, screenHeight/2 - player.y);
        graph.lineTo(gameWidth + screenWidth/2 - player.x, gameHeight + screenHeight/2 - player.y);
        graph.strokeStyle = lineColor;
        graph.stroke();
    }

    // Bottom-horizontal.
    if (gameHeight - player.y <= screenHeight/2) {
        graph.beginPath();
        graph.moveTo(gameWidth + screenWidth/2 - player.x, gameHeight + screenHeight/2 - player.y);
        graph.lineTo(screenWidth/2 - player.x, gameHeight + screenHeight/2 - player.y);
        graph.strokeStyle = lineColor;
        graph.stroke();
    }
}

window.requestAnimFrame = (function() {
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.msRequestAnimationFrame     ||
            function( callback ) {
                window.setTimeout(callback, 1000 / 60);
            };
})();

window.cancelAnimFrame = (function(handle) {
    return  window.cancelAnimationFrame     ||
            window.mozCancelAnimationFrame;
})();

function animloop() {
    animLoopHandle = window.requestAnimFrame(animloop);
    gameLoop();
}

function gameLoop() {
    if (died) {
        graph.fillStyle = '#333333';
        graph.fillRect(0, 0, screenWidth, screenHeight);

        graph.textAlign = 'center';
        graph.fillStyle = '#FFFFFF';
        graph.font = 'bold 30px sans-serif';
        graph.fillText('You died!', screenWidth / 2, screenHeight / 2);
    }
    else if (!disconnected) {
        if (gameStart) {
            graph.fillStyle = backgroundColor;
            graph.fillRect(0, 0, screenWidth, screenHeight);

            drawgrid();
            foods.forEach(drawFood);
            fireFood.forEach(drawFireFood);
            
            if (borderDraw) {
                drawborder();
            }
            var orderMass = [];
            for(var i=0; i<users.length; i++) {
                for(var j=0; j<users[i].cells.length; j++) {
                    orderMass.push({
                        nCell: i,
                        nDiv: j,
                        mass: users[i].cells[j].mass
                    });
                }
            }
            orderMass.sort(function(obj1,obj2) {
                return obj1.mass - obj2.mass;
            });

            drawPlayers(orderMass);

            viruses.forEach(drawVirus);

            if (player.cells.length > 0)
            {
                var playerRadius = player.cells.map(function (val, ind, arr) {
                    return val.radius;
                }).reduce(function (pl, val, ind, arr) {
                    return pl + val;
                }) / player.cells.length;

                var widthMax = gameWidth - playerRadius - 1,
                    heightMax = gameHeight - playerRadius - 1,
                    min = playerRadius + 1;

                realTarget.x = realTarget.x > widthMax ? widthMax : realTarget.x;
                realTarget.x = realTarget.x < min ? min : realTarget.x;
                realTarget.y = realTarget.y > heightMax ? heightMax : realTarget.y;
                realTarget.y = realTarget.y < min ? min : realTarget.y;

                var tx = realTarget.x - player.x,
                    ty = realTarget.y - player.y,
                    playerPosError = window.game.posError * player.cells.length;

                target.x = Math.abs(tx) > playerPosError ? tx : 0;
                target.y = Math.abs(ty) > playerPosError ? ty : 0;
            }

            socket.emit('0', target); // playerSendTarget "Heartbeat".

        } else {
            graph.fillStyle = '#333333';
            graph.fillRect(0, 0, screenWidth, screenHeight);

            graph.textAlign = 'center';
            graph.fillStyle = '#FFFFFF';
            graph.font = 'bold 30px sans-serif';
            graph.fillText('Game Over!', screenWidth / 2, screenHeight / 2);
        }
    } else {
        graph.fillStyle = '#333333';
        graph.fillRect(0, 0, screenWidth, screenHeight);

        graph.textAlign = 'center';
        graph.fillStyle = '#FFFFFF';
        graph.font = 'bold 30px sans-serif';
        if (kicked) {
            if (reason !== '') {
                graph.fillText('You were kicked for:', screenWidth / 2, screenHeight / 2 - 20);
                graph.fillText(reason, screenWidth / 2, screenHeight / 2 + 20);
            }
            else {
                graph.fillText('You were kicked!', screenWidth / 2, screenHeight / 2);
            }
        }
        else {
              graph.fillText('Disconnected!', screenWidth / 2, screenHeight / 2);
        }
    }
}

window.addEventListener('resize', resize);

function resizeGameArea() {
    player.screenWidth = c.width = screenWidth = playerType == 'player' ? window.innerWidth : gameWidth;
    player.screenHeight = c.height = screenHeight = playerType == 'player' ? window.innerHeight : gameHeight;
}

function resize() {
    resizeGameArea();
    editor.resize();
}

window.game = {};

window.game.posError = 3;

window.game.foods = function () {
    return foods.map(function (val, ind, arr) {
        return {
            x: val.x,
            y: val.y,
            radius: val.radius,
            mass: val.mass
        };
    });
};

window.game.viruses = function () {
    return viruses.map(function (val, ind, arr) {
        return {
            x: val.x,
            y: val.y,
            radius: val.radius,
            mass: val.mass
        };
    });
};

window.game.ejectedMasses = function () {
    return fireFood.map(function (val, ind, arr) {
        return {
            owner: val.id,
            x: val.x,
            y: val.y,
            radius: val.radius,
            mass: val.masa,
            target: {
                x: val.target.x,
                y: val.target.y
            }
        };
    });
};

window.game.otherCells = function () {
    return [].concat.apply([], users.map(function (val, ind, arr) {
        if (typeof(val.id) == "undefined")
        {
            return undefined;
        }
        return val.cells.map(function (cell, cid, arr) {
            return {
                owner: val.id,
                x: cell.x,
                y: cell.y,
                radius: cell.radius,
                mass: cell.mass,
            };
        });
    }).filter(function (val, ind, arr) {
        return typeof(val) != "undefined";
    }));
};

window.game.ownCells = function () {
    return users.filter(function (val, ind, arr) {
        return typeof(val.id) == "undefined";
    })[0].cells.map(function (cell, cid, arr) {
        return {
            x: cell.x,
            y: cell.y,
            radius: cell.radius,
            mass: cell.mass,
        };
    });
};

window.game.leaderboard = function () {
    return leaderboard.map(function (val, ind, arr) {
        return {
            id: val.id,
            name: val.name
        };
    });
};

window.game.getPos = function () {
    return {
        x: player.x,
        y: player.y
    };
};

window.game.setTarget = function (x, y) {
    realTarget.x = x;
    realTarget.y = y;
};

window.game.fireMass = function () {
    socket.emit('1');
};

window.game.split = function () {
    socket.emit('2');
};

window.game.setRunAtUpdate = function (fun) {
    userCode = fun;
};

function coordinateIsZero(val) {
    var playerPosError = window.game.posError * player.cells.length;
    return val <= playerPosError && val >= -playerPosError;
}

window.game.allReachedTarget = function () {
    return coordinateIsZero(player.x - realTarget.x) && coordinateIsZero(player.y - realTarget.y);
};

window.game.oneCellReachedTarget = function () {
    return users.filter(function (val, ind, arr) {
        return typeof(val.id) == "undefined";
    })[0].cells.map(function (cell, ind, arr) {
        return window.game.pointInCircle(cell.x, cell.y, realTarget.x, realTarget.y, cell.radius);
    }).reduce(function (pl, val, ind, arr) {
        return pl || val;
    });
};

window.game.pointInCircle = function (circle_x, circle_y, point_x, point_y, radius) {
    return Math.pow(circle_x - point_x, 2) + Math.pow(circle_y - point_y, 2) < Math.pow(radius, 2);
};

window.game.log = function (stuff) {
    chat.addDebugLine(stuff);
};

function ready() {
    window.game.gameHeight = gameHeight;
    window.game.gameWidth = gameWidth;
}
